﻿/*    
Nemulation: A .NET based framework/platform for virtualization applications
Copyright (C) 2011 Bryan Perris

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using NEmu.Debugging;
using NEmu.Memory;
using System.Threading;
using System.Runtime.Serialization;
using System.Security;
using System.Globalization;
using System.Security.Cryptography;
using System.Reflection;

namespace NEmu
{
    /// <summary>
    /// Abstract class base for a virtual machine that will execute emulated code at runtime, and allocate virtual resources as needed.
    /// Each instance of a virtualmachine based class will all have runtime IDs, for indentifying seperate Vms at runtime.
    /// </summary>
    [Serializable]
    public abstract class VirtualMachine: IComparable<VirtualMachine>
    {
        // Private class variables
        private RunState m_State;
        private bool m_Booted = false;
        private string m_LogSectionID;
        private bool m_AllowDebugger = true;
        private int m_HashCode = 0;
        [NonSerialized]
        private IDebugger m_Debugger;

        // Events
        /// <summary>
        /// Fires when machine's run state has changed.  Recommend to override OnStateChanged for inheriting classes.
        /// </summary>
        public event EventHandler<RunStateChangedEventArgs> RunningStateChanged;

        protected VirtualMachine()
        {
            int timeHash = (int)DateTime.Now.Ticks ^ (366 - DateTime.Now.Date.DayOfYear);

            Guid guid;
            {
                object[] classGuid = this.GetType().GetCustomAttributes(typeof(Guid), true);

                if (classGuid.Length > 0)
                {
                    guid = (Guid)classGuid[0];
                }
                else
                {
                    object[] assemblyGuid = Assembly.GetAssembly(typeof(VirtualMachine)).GetCustomAttributes(typeof(Guid), true);

                    if (assemblyGuid.Length > 0)
                    {
                        guid = (Guid)assemblyGuid[0];
                    }
                    else
                    {
                        guid = Guid.NewGuid();
                    }
                }
            }

            m_HashCode = guid.GetHashCode() ^ timeHash;
        }

        /// <summary>
        /// Removes the section ID from the log
        /// Note: This clsas should implement IDisposable
        /// </summary>
        ~VirtualMachine()
        {
            EmuLog.RemoveSectionName(m_LogSectionID);
        }

        #region Machine API

        /// <summary>
        /// Starts the machine, with bootup sequence if not booted yet
        /// </summary>
        public void Run()
        {
            SetRunState(RunState.Running);
        }

        /// <summary>
        /// Pauses the execution cycle of the virtual machine, not to be confused with saving state.
        /// </summary>
        public void Pause()
        {
            SetRunState(RunState.Paused);
        }

        /// <summary>
        /// Shutdowns the machines and expects all internal machine resources to be released to ensure a properly disposed machine and/or
        /// able to boot up again with no conflict of resources in use and thread collisions.
        /// </summary>
        public void Stop()
        {
            SetRunState(RunState.Stopped);
        }

        /// <summary>
        /// Restarts the machine by calling Stop, then Run with a 0.1 second thread execution delay after stop has been called.
        /// </summary>
        public void Restart()
        {
            EmuLog.InfoLog("Restarting machine...");
            Stop();
            Thread.Sleep(100);
            Run();
        }

        /// <summary>
        /// Attached a class that inherits IDebugger, for allow to debug the virtual machine.
        /// </summary>
        /// <param name="debugger">An instance inheriting the IDebugger interface used for debug time</param>
        public void AttachDebugger(IDebugger debugger)
        {
            if (m_AllowDebugger)
            {
                m_Debugger = debugger;
                EmuLog.InfoLog("Attached debugger: " + debugger.ToString());
            }
        }

        /// <summary>
        /// Writes to the NEmu log system under under the virtual machine's log section
        /// </summary>
        /// <param name="level">The level of verboisity of the log message</param>
        /// <param name="message">The message to write to the log</param>
        public void LogMessage(LogLevel level, string message)
        {
            EmuLog.SectionLog(m_LogSectionID, level, message);
        }

        /// <summary>
        /// Overriden. Compares virtual machines to other virtual machines
        /// </summary>
        /// <param name="obj">reference to an instance of a virtual machine based object</param>
        /// <returns>Returns whether obj is equal to the object it is being compared against</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (ReferenceEquals(this, obj))
                return true;

            if (obj is VirtualMachine)
            {
                if (CompareTo((VirtualMachine)obj) == 0)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Generates a unique hashcode based on a Guid hashcode of the class else if not, then the current assembly itself.  Then
        /// Xors a time hashcode to it.
        /// 
        /// Exceptions:
        /// InvalidOperationException
        /// </summary>
        /// <returns>Returns the generated hashcode</returns>
        public override int GetHashCode()
        {
            return m_HashCode;
        }

        public void SetState(RunState state)
        {
            SetRunState(state);
        }

        #endregion

        #region Abstract Methods

        /// <summary>
        /// Executes the boot squence for the virtual machine
        /// </summary>
        protected abstract void Boot();

        /// <summary>
        /// Initializes all the interal components of the virtual machine
        /// </summary>
        protected abstract void InitializeComponents();

        /// <summary>
        /// Shutsdown and disposes all the internal componenets of the virtual machine
        /// </summary>
        protected abstract void ShutdownComponents();

        #endregion

        #region Virtual Methods

        /// <summary>
        /// Fires the RunningStateChanged event
        /// </summary>
        /// <param name="state">The state of which the machine was set to</param>
        protected virtual void OnStateChanged(RunState state)
        {
            if (RunningStateChanged != null)
                RunningStateChanged(this, new RunStateChangedEventArgs(state));
        }

        #endregion

        #region Private Methods

        private void SetRunState(RunState state)
        {
            LogMessage(LogLevel.Info, "Machine state set to " + state.ToString());

            if (state != RunState.Running && m_State == RunState.Stopped)
                return;

            m_State = state;

            #region Pause State

            if (state == RunState.Paused)
            {
                if (m_Debugger != null)
                    m_Debugger.Report();
                LogMessage(LogLevel.Info, "Debugger report");
            }

            #endregion

            #region Bootup/Run State
            // Start point of the Machine boot process
            if (m_State == RunState.Running && !m_Booted)
            {
                m_LogSectionID = "VM_" + this.GetHashCode().ToString("X", CultureInfo.CurrentCulture);
                EmuLog.InfoLog("Machine [" + m_LogSectionID + "] booting");
                EmuLog.AddSectionName(m_LogSectionID);

                // First tell the machine to initilize subcomponents
                InitializeComponents();
                LogMessage(LogLevel.Info, "Components successfully initialized");

                // Tell the machine to load in media resources (disk, etc)
                try
                {
                    Resources.LoadResources();
                    LogMessage(LogLevel.Info, "Resources loaded sucessfully");
                }
                catch (ResourceException e)
                {
                    SetStop();
                    throw new VMException("Machine failed to load resources!", e);
                }

                // Boot up machine
                try
                {
                    Boot();
                    m_Booted = true;
                    LogMessage(LogLevel.Info, "Machine booted successfully");

                    if (m_Debugger != null)
                    {
                        m_Debugger.StartDebugging(this);
                        m_Debugger.Report();
                    }
                }
                catch (BootException e2)
                {
                    SetStop();
                    throw new VMException("Machine failed to boot!", e2);
                }

            }

            #endregion

            #region Stop State
            if (m_State == RunState.Stopped)
            {
                // Set isBooted false
                m_Booted = false;

                // Stop debugging
                if (m_Debugger != null)
                    m_Debugger.StopDebugging();

                // Shutdown all VM components
                LogMessage(LogLevel.Info, "shutting down components...");
                ShutdownComponents();

                // Shutdown Memory
                LogMessage(LogLevel.Info, "Disposing memory...");
                this.MachineMemory.Dispose();
                LogMessage(LogLevel.Info, "Disposed memory!");

                LogMessage(LogLevel.Info, "The machine has stopped!");
            }
            #endregion

            OnStateChanged(m_State);

        }

        private void SetStop()
        {
            m_State = RunState.Stopped;
            m_Booted = false;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The current running state of the machine, this is readonly.
        /// </summary>
        public RunState CurrentRunState
        {
            get { return m_State; }
        }

        /// <summary>
        /// The resource manager assigned to the current machine
        /// </summary>
        public abstract ResourceManager Resources { get; }

        /// <summary>
        /// The current memory system the virtual machine untilizes
        /// </summary>
        public abstract MemoryUnit MachineMemory { get; }

        /// <summary>
        /// Whether the machine has booted up.
        /// </summary>
        public bool IsBooted
        {
            get { return m_Booted; }
        }

        /// <summary>
        /// The current attached IDebugger object used for runtime debugging
        /// </summary>
        public IDebugger AttachedDebugger
        {
            get { return m_Debugger; }
        }

        /// <summary>
        /// Whether the virtual machine allows attachment of a debugger.  Allowed by defualt, disable
        /// to prevent an IDebugger class peeking into protected runtime resources or prevent users from
        /// gaining access to the core of the running virtual machine.  This must be set before AttachDebugger is called.
        /// This only applies if the machine is setup to run in debug mode.
        /// </summary>
        protected bool AllowDebuggerAttachment
        {
            get { return m_AllowDebugger; }
            set { m_AllowDebugger = value; }
        }

        public string MachineID
        {
            get { return m_LogSectionID; }
        }

        #endregion

        #region IComparable<VirtualMachine> Members


        /// <summary>
        /// Compares a virtual machien to this instance of a virtual machine
        /// </summary>
        /// <param name="other">the other virtual machine to compare</param>
        /// <returns>Returns the result of the comparison</returns>
        public int CompareTo(VirtualMachine other)
        {
            if (other != null)
                return this.m_LogSectionID.CompareTo(other.m_LogSectionID);
            else
                throw new ArgumentNullException("other");
        }

        #endregion
    }
}
